#ifndef _ASM_HPP_
#define _ASM_HPP_
#include<vector>
#include<string>
#include<fstream>
#include"SymbolTable.hpp"
#include"Register.hpp"
using namespace std;


enum Location{
    CONSTANT,       // 常量
    REGISTER,       // 寄存器
    MEMORY,         // 变量
    RETURN          // 函数调用
};

struct ASM{

    static char buffer[1024];
    static ofstream outputFile;
    static void setFile(const string& filePath){
        outputFile.open(filePath);
    }
    static void global(const string& label){
        sprintf(buffer,"    global  %s                 \n",label.data());   outputFile << buffer;
    }
    static void extern_label(const string& label){
        sprintf(buffer,"    extern  %s                 \n",label.data());   outputFile << buffer;
    }

    static void function_entry(int size){
        sprintf(buffer,"    push    ebp                 \n");   outputFile << buffer;
        sprintf(buffer,"    mov     ebp, esp            \n");   outputFile << buffer;
        sub_esp(size);
    }

    static void function_return(){
        sprintf(buffer,"    mov     esp, ebp            \n");   outputFile << buffer;
        sprintf(buffer,"    pop     ebp                 \n");   outputFile << buffer;
        sprintf(buffer,"    ret                         \n");   outputFile << buffer;
    }

    static void add_esp(int size){
        size = (size + STACK_BYTES_ALIGNMENT - 1)/STACK_BYTES_ALIGNMENT*STACK_BYTES_ALIGNMENT;
        sprintf(buffer,"    add     esp, %d             \n",size);   
        outputFile << buffer;
    }
    static void sub_esp(int size){
        size = (size + STACK_BYTES_ALIGNMENT - 1)/STACK_BYTES_ALIGNMENT*STACK_BYTES_ALIGNMENT;
        sprintf(buffer,"    sub     esp, %d             \n",size);   
        outputFile << buffer;

    }

    static void push(const string& val){
        sprintf(buffer,"    push    %s                  \n",val.data());   
        outputFile << buffer;
    }

    static void push_constant(const string& val){
        sprintf(buffer,"    push    %s                  \n",val.data());   
        outputFile << buffer;
    }
    static void push_register(const Register& reg){
        sprintf(buffer,"    push    %s                  \n",reg.getRegister32().data());   
        outputFile << buffer;
    }



    static void push(const vector<string>& val,const Register& reg,TypeInfo* typeInfo,Location location){

        switch(location){
            case Location::CONSTANT:
                push_constant(val.front());
                break;
            case Location::REGISTER:
                push_register(reg);
                break;
            case Location::MEMORY:
                for (auto& v : val){
                    move_mem_to_reg(v,reg,typeInfo);
                    push_register(reg);
                }
                break;
            case Location::RETURN:
                break;
        }

    }

    static void setString(const string& str){
        sprintf(buffer,"    db  %s,0x0A,0                        \n",str.data());
        outputFile << buffer;
    }

    static void setLabel(const string& label){
        sprintf(buffer,"%s:                                \n",label.data());
        outputFile << buffer;
    }

    static void setFunctionLabel(const string& className,const string& functionName){
        string label = className + "$" + functionName;
        sprintf(buffer,"\n\n%s:                                \n",label.data());
        outputFile << buffer;
    }

    static void callFunction(const string& className,const string& functionName){
        string label = className.empty() ? functionName : className + "$" + functionName;
        sprintf(buffer,"    call    %s\n",label.data());
        outputFile << buffer;
    }

    static void move_this_to_esi(const string& val){
        sprintf(buffer,"    mov     esi, %s          \n",val.data()); 
        outputFile << buffer;
    }
    // movzx movsx
    static void move_mem_to_reg(const string& val,const Register& reg,TypeInfo* typeInfo){
        switch (typeInfo->typeKind)
        {
        case TypeKind::BOOL_TYPE:
            sprintf(buffer,"    movzx   %s, BYTE %s     \n",reg.getRegister32().data(),val.data()); outputFile << buffer;
            break;
        case TypeKind::INT8_TYPE:
            sprintf(buffer,"    movzx   %s, BYTE %s     \n",reg.getRegister32().data(),val.data()); outputFile << buffer;
            break;
        case TypeKind::UINT8_TYPE:
            sprintf(buffer,"    movzx   %s, BYTE %s     \n",reg.getRegister32().data(),val.data()); outputFile << buffer;
            break;
        case TypeKind::INT16_TYPE:
            sprintf(buffer,"    movzx   %s, WORD %s     \n",reg.getRegister32().data(),val.data()); outputFile << buffer;
            break;
        case TypeKind::UINT16_TYPE:
            sprintf(buffer,"    movzx   %s, WORD %s     \n",reg.getRegister32().data(),val.data()); outputFile << buffer;
            break;
        case TypeKind::INT32_TYPE:
            sprintf(buffer,"    mov     %s, DWORD %s     \n",reg.getRegister32().data(),val.data());outputFile << buffer;
            break;
        case TypeKind::UINT32_TYPE:
            sprintf(buffer,"    mov     %s, DWORD %s     \n",reg.getRegister32().data(),val.data());outputFile << buffer;
            break;
        

        case TypeKind::CLASS_TYPE:
            sprintf(buffer,"    mov     %s, DWORD %s     \n",reg.getRegister32().data(),val.data());outputFile << buffer;
            break;
        default:
            break;
        }
    }



    static void move_temp_to_reg(const string& val,const Register& reg,TypeInfo* typeInfo){
        switch (typeInfo->typeKind)
        {
        case TypeKind::BOOL_TYPE:
        case TypeKind::INT8_TYPE:
        case TypeKind::UINT8_TYPE:
        case TypeKind::INT16_TYPE:
        case TypeKind::UINT16_TYPE:
        case TypeKind::INT32_TYPE:
        case TypeKind::UINT32_TYPE:
            sprintf(buffer,"    mov     %s, %s     \n",reg.getRegister32().data(),val.data());
            outputFile << buffer;
            break;
        default:
            break;
        }
    }

    static void move_reg_to_mem(const string& val,Register reg,TypeInfo* typeInfo){
        switch (typeInfo->typeKind)
        {
        case TypeKind::BOOL_TYPE:
            sprintf(buffer,"    mov     BYTE %s, %s     \n",val.data(),reg.getRegister8().data());  outputFile << buffer;
            break;
        case TypeKind::INT8_TYPE:
            sprintf(buffer,"    mov     BYTE %s, %s     \n",val.data(),reg.getRegister8().data());  outputFile << buffer;
            break;
        case TypeKind::UINT8_TYPE:
            sprintf(buffer,"    mov     BYTE %s, %s     \n",val.data(),reg.getRegister8().data());  outputFile << buffer;
            break;
        case TypeKind::INT16_TYPE:
            sprintf(buffer,"    mov     WORD %s, %s      \n",val.data(),reg.getRegister16().data());outputFile << buffer;
            break;
        case TypeKind::UINT16_TYPE:
            sprintf(buffer,"    mov     WORD %s, %s      \n",val.data(),reg.getRegister16().data());outputFile << buffer;
            break;
        case TypeKind::INT32_TYPE:
            sprintf(buffer,"    mov     DWORD %s, %s      \n",val.data(),reg.getRegister32().data());outputFile << buffer;
            break;
        case TypeKind::UINT32_TYPE:
            sprintf(buffer,"    mov     DWORD %s, %s      \n",val.data(),reg.getRegister32().data());outputFile << buffer;
            break;

        
        case TypeKind::CLASS_TYPE:
            sprintf(buffer,"    mov    DWORD %s, %s      \n",val.data(),reg.getRegister32().data());outputFile << buffer;
            break;
        default:
            break;
        }
    }

    static void add(const Register& reg1,const Register& reg2){
        sprintf(buffer,"    add     %s, %s      \n",reg1.getRegister32().data(),reg2.getRegister32().data());
        outputFile << buffer;
    }

    static void sub(const Register& reg1,const Register& reg2){
        sprintf(buffer,"    sub     %s, %s      \n",reg1.getRegister32().data(),reg2.getRegister32().data());
        outputFile << buffer;
    }

    
    static void imul(Register& reg1,Register& reg2){
        sprintf(buffer,"    imul    %s, %s      \n",reg1.getRegister32().data(),reg2.getRegister32().data());
        outputFile << buffer;
    }

    static void div(Register& reg1,Register& reg2){
        if (reg1.getRegisterKind() != RegisterKind::EAX_AX_AL)
            swapRegister(reg1,RegisterKind::EAX_AX_AL);
        if (reg2.getRegisterKind() == RegisterKind::EDX_DX_DL)
            swapRegister(reg2,RegisterKind::EBX_BX_BL);

        sprintf(buffer,"    push    edx        \n");
        outputFile << buffer;
        sprintf(buffer,"    mov     edx, 0     \n");
        outputFile << buffer;
        sprintf(buffer,"    div     %s         \n",reg2.getRegister32().data());
        outputFile << buffer;
        sprintf(buffer,"    pop     edx        \n");
        outputFile << buffer;
    }

    static void bit_and(Register& reg1,Register& reg2){
        sprintf(buffer,"    and     %s, %s      \n",reg1.getRegister32().data(),reg2.getRegister32().data());
        outputFile << buffer;
    }

    static void bit_or(Register& reg1,Register& reg2){
        sprintf(buffer,"    or      %s, %s      \n",reg1.getRegister32().data(),reg2.getRegister32().data());
        outputFile << buffer;
    }

    static void bit_not(Register& reg){
        sprintf(buffer,"    not     %s          \n",reg.getRegister32().data());
        outputFile << buffer;
    }

    static void swapRegister(Register& reg1,RegisterKind registerKind){
        sprintf(buffer,"    push    %s                      \n",reg1.getRegister32().data());   outputFile << buffer;
        sprintf(buffer,"    mov     %s, %s                  \n",reg1.getRegister32().data(),Register::getRegister32(registerKind).data());    outputFile << buffer;
        sprintf(buffer,"    pop     %s                      \n",Register::getRegister32(registerKind).data());    outputFile << buffer;
        
        reg1.swapRegister(registerKind);
    }

    static void lea(Register& reg, const string& address){
        sprintf(buffer,"    lea    %s, %s                   \n",reg.getRegister32().data(),address.data());   
        outputFile << buffer;
    }

    static void cmp(Register& reg1,Register& reg2){
        sprintf(buffer,"    cmp     %s, %s                  \n",reg1.getRegister32().data(),reg2.getRegister32().data());
        outputFile << buffer;
    }

    static void cmp(Register& reg,long long num){
        sprintf(buffer,"    cmp     %s, %ld                  \n",reg.getRegister32().data(),num);
        outputFile << buffer;
    }

    // <=
    static void setle(Register& reg){
        sprintf(buffer,"    setle   %s                      \n",reg.getRegister8().data());
        outputFile << buffer;
    }

    // <
    static void setl(Register& reg){
        sprintf(buffer,"    setl    %s                      \n",reg.getRegister8().data());
        outputFile << buffer;
    }

    // ==
    static void sete(Register& reg){
        sprintf(buffer,"    sete    %s                      \n",reg.getRegister8().data());
        outputFile << buffer;
    }

    // !=
    static void setne(Register& reg){
        sprintf(buffer,"    setne    %s                      \n",reg.getRegister8().data());
        outputFile << buffer;
    }

    // >=
    static void setge(Register& reg){
        sprintf(buffer,"    setge    %s                      \n",reg.getRegister8().data());
        outputFile << buffer;                           
    }                                           
 //                                         
    // >                                            
    static void setg(Register& reg){            
        sprintf(buffer,"    setg    %s                      \n",reg.getRegister8().data());
        outputFile << buffer;
    }

    static void je(Register& reg,const string& label){
        sprintf(buffer,"    je      %s                      \n",label.data());
        outputFile << buffer;
    }

    static void jne(Register& reg,const string& label){
        sprintf(buffer,"    jne     %s                      \n",label.data());
        outputFile << buffer;
    }

    static void jmp(const string& label){
        sprintf(buffer,"    jmp     %s                      \n",label.data());
        outputFile << buffer;
    }

    static string getStringLabel(){
        static unsigned long long cnt = 0;
        string label = "STRING" + to_string(cnt);
        ++cnt;
        return label;
    }

    static string getRandomLabel(){
        static unsigned long long cnt = 0;
        string label = ".L" + to_string(cnt);
        ++cnt;
        return label;
    }
};

inline char ASM::buffer[1024];
inline ofstream ASM::outputFile;

#endif